/*
	binfile.cc	Binary File I/O
	Copyright (c) 2004 Kriang Lerdsuwanakij
	email:		lerdsuwa@users.sourceforge.net

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "binfile.h"
#include "gtstream.h"

#include <cstring>
#include <stdexcept>
#include <iostream>

#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>

#ifdef _
#undef _
#endif

#ifdef N_
#undef N_
#endif

#include <libintl.h>
#define _(x) gettext(x)
#define N_(x) (x)

const std::size_t binary_file_input::max_buffer_size;

binary_file_input::binary_file_input(const std::string &f)
{
	file = f;
	handle = open(f.c_str(), O_RDONLY | O_BINARY);
	if (handle == -1) {
		gtstream bufstr;
		gtout(bufstr, _("cannot open file %$\n")) << f;
		throw std::runtime_error(bufstr.str());
	}
	buffer_size = 0;
	buffer_ptr = 0;
	end_of_file = false;
}

binary_file_input::~binary_file_input()
{
	if (handle != -1) {
		close(handle);
		handle = -1;
	}
}

void	binary_file_input::fill_buffer()
{
	std::size_t	size_in_buffer = buffer_size - buffer_ptr;
	if (size_in_buffer)
		memmove(buffer, buffer+buffer_ptr, size_in_buffer);

				// Make sure state is sane because
				// code below may throw exception
	buffer_ptr = 0;
	buffer_size = size_in_buffer;

	if (end_of_file)
		return;

	int	size_to_read = max_buffer_size - size_in_buffer;
	int	size_read = read(handle, buffer+size_in_buffer, size_to_read);
	if (size_read == -1) {
		gtstream bufstr;
		gtout(bufstr, _("cannot read file %$\n")) << file;
		throw std::runtime_error(bufstr.str());
	}

	if (size_read != size_to_read)
		end_of_file = true;
	buffer_size += size_read;
}

void	binary_file_input::fill_buffer_if_required(std::size_t s)
{
	if (buffer_size - buffer_ptr < s)
		fill_buffer();
	if (buffer_size - buffer_ptr < s) {
		gtstream bufstr;
		gtout(bufstr, _("cannot read file %$\n")) << file;
		throw std::runtime_error(bufstr.str());
	}
}

unsigned char	binary_file_input::read_unsigned_char()
{
				// Fixed by data file format
	const std::size_t count = 1;
	fill_buffer_if_required(count);
	return read_unsigned_char_nocheck();
}

int	binary_file_input::read_int()
{
				// Fixed by data file format
	const std::size_t count = 4;
	fill_buffer_if_required(count);

	int i = 0;
	for (std::size_t j = 0; j < count; ++j) {
		i |= (static_cast<int>(read_unsigned_char_nocheck()) << (j*8));
	}
	return i;
}

unsigned	binary_file_input::read_unsigned_compress()
{
	unsigned i = 0;
	for (std::size_t j = 0; ; ++j) {
		unsigned char c = read_unsigned_char();
		i |= (static_cast<unsigned>(c & 0x7F) << (j*7));
		if ((c & 0x80) == 0)
			break;
	}
	return i;
}

void	binary_file_input::read_board(byte_board_info& b)
{
				// Fixed by data file format
	const std::size_t count = 16;
	fill_buffer_if_required(count);

	char	buf[16];
	for (int i = 0; i < 16; ++i)
		buf[i] = read_unsigned_char_nocheck();

	byte_board_type bb;
	for (int i = 0; i < 8; ++i) {
		for (int j = 0; j < 8; ++j) {
			int pos = xy_to_pos(i, j);
			if (buf[i] & (1<<j)) {
				if (buf[i+8] & (1<<j))
					bb[pos] = WHITE;
				else
					bb[pos] = BLACK;
			}
			else
				bb[pos] = EMPTY;
		}
	}

	b = &bb;
}


const std::size_t binary_file_output::max_buffer_size;

binary_file_output::binary_file_output(const std::string &f)
{
	file = f;
	handle = creat(f.c_str(), S_IRUSR | S_IWUSR);

	if (handle == -1) {
		gtstream bufstr;
		gtout(bufstr, _("cannot create file %$\n")) << f;
		throw std::runtime_error(bufstr.str());
	}
	buffer_size = 0;
}

binary_file_output::~binary_file_output()
{
	if (handle != -1) {
		flush_buffer();
		close(handle);
		handle = -1;
	}
}

void	binary_file_output::flush_buffer()
{
	if (!buffer_size)
		return;

				// Make sure state is sane because
	int	size_write = write(handle, buffer, buffer_size);
	if (size_write == -1
	    || size_write != static_cast<int>(buffer_size)) {
				// Make sure we don't flush in destructor
		close(handle);
		handle = -1;

		gtstream bufstr;
		gtout(bufstr, _("cannot write file %$\n")) << file;
		throw std::runtime_error(bufstr.str());
	}

	buffer_size = 0;
}

void	binary_file_output::flush_buffer_if_required(std::size_t s)
{
	if (max_buffer_size - buffer_size < s)
		flush_buffer();
}

void	binary_file_output::write_unsigned_char(unsigned char c)
{
				// Fixed by data file format
	const std::size_t count = 1;
	flush_buffer_if_required(count);

	write_unsigned_char_nocheck(c);
}

void	binary_file_output::write_int(int i)
{
				// Fixed by data file format
	const std::size_t count = 4;
	flush_buffer_if_required(count);

	for (std::size_t j = 0; j < count; ++j) {
		write_unsigned_char_nocheck(static_cast<unsigned char>(i & 0xFF));
		i >>= 8;
	}
}

void	binary_file_output::write_unsigned_compress(unsigned i)
{
	do {
		unsigned char buf = static_cast<unsigned char>(i & 0x7F);
		if (i > 0x7F)
			buf |= 0x80;
		write_unsigned_char(buf);
		i >>= 7;
	} while (i);
}

void	binary_file_output::write_board(const byte_board_info& b)
{
				// Fixed by data file format
	const std::size_t count = 16;
	flush_buffer_if_required(count);

	unsigned char	buf[16];
	for (int i = 0; i < 16; ++i)
		buf[i] = 0;

	for (int i = 0; i < 8; ++i) {
		for (int j = 0; j < 8; ++j) {
			int pos = xy_to_pos(i, j);
			switch(b.board[pos]) {
				case BLACK:
					buf[i] |= 1<<j;
					break;
				case WHITE:
					buf[i] |= 1<<j;
					buf[i+8] |= 1<<j;
					break;
			}
		}
	}

	for (int i = 0; i < 16; ++i)
		write_unsigned_char_nocheck(buf[i]);
}
